package domain.tc;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import tableaux.ClashTrigger;
import utils.exception.ConcreteDomainException;

import domain.tc.TCNode.EdgeType;

import entities.concept.DomainConcept;

public class InitDomainGraph {
	private Set<DomainConcept<?, ?>> dcs;
	private Set<String> domainStrings;
	private boolean initialClash = false;
	public static int called = 0;
	public static long timeSpend = 0;
	public static long graphsize = 0;
	private ClashTrigger trigger;

	public InitDomainGraph(Set<DomainConcept<?, ?>> dcs, ClashTrigger ct) {
		InitGraphAccessor.setDefault(new InitGraphAccessorImpl(this));
		this.trigger = ct;
		this.dcs = new HashSet<DomainConcept<?, ?>>();
		this.dcs.addAll(dcs);
		this.domainStrings = new HashSet<String>();
	}

	public ClashTrigger getAssociatedClashTrigger() {
		return this.trigger;
	}

	// return true if a clash is found
	public boolean clashFinder() {
		boolean safe = true;
		// important! must clear all nodes from previous runs
		TCNode.clearAllNodes();
		this.build();
		if (this.initialClash) {
			return true;
		} else {
			// visit and compute all nodes
			Set<TCNode> allnodes = new HashSet<TCNode>();
			allnodes.addAll(TCNode.getAllNodes());
			graphsize += TCNode.getAllNodes().size();
			this.computeAllClosures(allnodes);
			// check safety for all components
			safe &= this.safeComponent(TCNode.getAllNodes());
			// System.out.println(this.dcs+"\nAbove set safe?" + safe);
		}
		return !safe;
	}

	// for all nodes, only compute closures for those not visited
	private void computeAllClosures(Collection<TCNode> givenNodes) {
		// System.out.printf("[computeTC call %d] on (%d) nodes\n", ++called,
		// givenNodes.size());
		TCClashFinder tc = new TCClashFinder();
		TCNode firstnode = null;
		if (givenNodes.size() > 0) {
			firstnode = givenNodes.iterator().next();
		} else {
			return;
		}
		// do the first pass
		tc.computeTransitiveClosure(firstnode);
		// do TC for those not yet visited
		givenNodes.removeAll(tc.getVisitedNodes());
		if (givenNodes.size() > 0) {
			this.computeAllClosures(givenNodes);
		}
	}

	// check if the node's component is a safe one, i.e., not both LT edge and
	// EQ egdes
	private boolean safeComponent(Collection<TCNode> allnodes) {
		boolean safe = true;
		// check if safe for all visited nodes
		Set<TCNode> sccnodes = new HashSet<TCNode>();
		sccnodes.addAll(allnodes);
		TCNode firstnode = null;
		if (allnodes.size() > 0) {
			firstnode = sccnodes.iterator().next();
			List<TCNode> onecomp = firstnode.scc.content;
			// System.out.println(firstnode+" -> {"+onecomp+"}");
			if (onecomp.size() > 1 && onecomp.contains(firstnode)) {
				// added on Aug. 27, 2012
				// if a comp contains the node itself, and the comp is built by
				// at least one < edge, then self loop f<f
				safe &= this.checkOneComp(onecomp, true);
			} else {
				safe &= this.checkOneComp(onecomp, false);
			}
			if (!safe) {
				// early cut
				return false;
			}
			// check other components
			sccnodes.removeAll(onecomp);
			safe &= this.safeComponent(sccnodes);
		}
		return safe;
	}

	// given a list of nodes in the same component, check if the single
	// component is safe
	// return true if safe
	private boolean checkOneComp(List<TCNode> onecomp, boolean ltClash) {
		boolean hasLTEdge = false;
		boolean hasEQEdge = false;
		for (TCNode u : onecomp) {
			Set<TCNode> lts = u.getLTEdges();
			Set<TCNode> es = u.getEQEdges();
			if (lts.size() > 0) {
				hasLTEdge |= !Collections.disjoint(onecomp, lts);
			}
			if (es.size() > 0) {
				hasEQEdge |= !Collections.disjoint(onecomp, es);
			}
			if (ltClash && hasLTEdge) {
				return false;
			}
			if (hasLTEdge && hasEQEdge) {
				return false;
			}
		}
		return true;
	}

	private boolean build() {
		// first build initial edges
		for (DomainConcept<?, ?> dc : dcs) {
			this.initialClash |= this.populateEdge(dc.getLHS(), dc.getOpType(),
					dc.getValue());
			int count = 0;
			if (!dc.isLHSRole()) {
				count++;
				this.domainStrings.add(dc.getLHS().toString());
			}
			if (!dc.isRHSRole()) {
				count++;
				this.domainStrings.add(dc.getValue().toString());
			}
			if (count == 2) {
				// two strings
				String s1 = (String) dc.getLHS();
				String s2 = (String) dc.getValue();
				int res = s1.compareTo(s2);
				if (res > 0 || res == 0 && dc.getOpType().equals(EdgeType.LT)
						|| res < 0 && dc.getOpType().equals(EdgeType.EQ)) {
					initialClash |= true;
				}
			}
			if (this.initialClash) {
				return true;
			}
		}
		// build extra edges using all constants
		if (this.domainStrings.size() > 1) {
			Set<String> ds = new HashSet<String>();
			ds.addAll(domainStrings);
			this.initialClash |= this.buildDomainConcepts(ds);
		}
		return this.initialClash;
	}

	// returns true if an initial clash found
	private boolean populateEdge(Object u, EdgeType op, Object v) {
		TCNode tu = null;
		try {
			tu = TCNode.getInstance(u);
		} catch (ConcreteDomainException e) {
			e.printStackTrace();
		}
		return tu.addEdge(op, v);
	}

	// return true if a clash is found
	private boolean buildDomainConcepts(Set<String> ds) {
		while (ds.size() > 1) {
			String s = ds.iterator().next();
			ds.remove(s);
			for (String c : ds) {
				int res = s.compareTo(c);
				if (res == 0) {
					this.initialClash |= this.populateEdge(s, EdgeType.EQ, c);
				} else if (res > 0) {
					this.initialClash |= this.populateEdge(c, EdgeType.LT, s);
				} else if (res < 0) {
					this.initialClash |= this.populateEdge(s, EdgeType.LT, c);
				}
			}
		}
		return this.initialClash;
	}

}
